package com.googlecode.groovy_toy_orm


/**
 * @author: DavydovMD
 * Date: 28.03.13
 * Time: 16:19
 */


class Mapper {
    protected MapperFields builder
    String keyField

    String getPrefix() {
        ""
    }

    Mapper(Mapper m) {
        this.builder = m.builder.copy()
        keyField = m.keyField
    }

    Mapper(MapperFields builder) {
        this.builder = builder
        if (builder)
            this.keyField = builder.fields.first().name
    }

    Mapper(Closure c) {
        builder = new MapperFields().build(c)
        if (builder)
            this.keyField = builder.fields.first().name
    }

    List<Field> getAllFields() {
        builder.fields
    }

    /**
     * First, checks if it's present in this mapper, otherwise exception is thrown!
     * @param fqName
     * @return
     */
    protected String simplifyFieldName(String fqName) {
        getField(fqName) //Will throw exception if field not present in the mapper
        Field.simplifyName(fqName)
    }

    Map transformMap(Map map) {
        Map ret = [:]
        List<Field> fields = this.builder.fields

        fields.each { Field f ->
            if (map.findAll { f.from.contains(it.key) }.size() == f.from.size()) {
                ret.put(f.name, f.transform(f, map))
            }

        }
        ret
    }

    Map transformMap(Map source, Map columnAliases) {
        if (!columnAliases) {
            return transformMap(source)
        }


        Map ret = [:]
        List<Field> fields = this.builder.fields

        fields.each { Field f ->
            Map fqNames = getFqSourceNames(f.name)

            // Find aliases for the field

            Map aliasesOfInterest = columnAliases.findAll { Map.Entry alias ->
                fqNames.containsValue(alias.key)
            }

            // De-alias row

            Map deAliased = new HashMap(source)
            aliasesOfInterest.each {
                Map.Entry alias ->
                    String columnName = fqNames.find { it == alias.key }.key

                    if (columnName) {
                        // Remove column with the name of aliased column, so that we can rename it back
                        deAliased.remove(columnName)
                        // De-alias
                        def value = deAliased.get(alias.key)
                        deAliased.remove(alias.key)
                        deAliased.put(columnName, value)
                    }

            }

            if (source.findAll { f.from.contains(it.key) }.size() == f.from.size()) {
                ret.put(f.name, f.transform(f, deAliased))
            }

        }
        ret
    }


    boolean isMultiColumn(String field) {
        getField(field).from.size() > 1
    }

    boolean isCalculated(String field) {
        getField(field).from.size() == 0
    }

    Object transformBackFieldValue(String field, Object value) {
        Field f = getField(field)
        Map m = f.transformBack(f, value)
        if (m.size() == 1) {
            return m.values().last()
        }
        throw new UnsupportedOperationException("Can't transform back field ${field} using value ${value}. transformBack returned ${m}")
    }

    Object getSourceName(String field) {
        Object from = getField(field).from
        if (from.size() != 1) return null
        from.last()
    }


    String getName() {
        "Mapper with fields [$allFields] and keyField ${keyField}"
    }

    String toString() {
        name
    }

    String getFqSourceName(String field) {
        getSourceName(field)
    }

    Map<String, String> getFqSourceNames(String field) {
        Map m = [:]
        getField(field).from.each { String s ->
            m.put(s, s)
        }
        m
    }

    Field getField(String inFieldName) {
        //TODO: !!!!
        String fieldName = Field.simplifyName(inFieldName)


        Field field = builder.getField(fieldName)

        if (!field) {
            throw new NoSuchFieldException("No such field (${fieldName}) defined in this mapper [${this}]")
        }
        field

    }

    def <T> Map mapBack(T bean) {
        Map ret = [:]
        builder.fields.each { Field f ->
            ret.putAll(f.transformBack(f, bean."${f.name}"))
        }
        ret
    }

    def <T> T update(T bean, Map source, Map columnAliases = [:]) {
        updateFrom(source, bean, columnAliases)
    }

    def <T> T updateFrom(Map source, T bean, Map columnAliases = [:]) {
        Map ret = transformMap(source, columnAliases)
        ret.each {
            try {
                bean."${it.key}" = it.value
            } catch (Exception e) {
                e.printStackTrace()
                throw new IllegalArgumentException("Failed to set field ${it.key} with ${it.value}. exception: ${e}")
            }
        }
        bean
    }

    def <T> T buildFrom(Map<String, Object> source, Class<T> resClass, Map columnAliases = [:]) {
        Map<String, Object> transformed

        transformed = transformMap(source, columnAliases)
        buildFromTranformed(transformed, resClass)
    }

    def <T> T buildFromTranformed(Map<String, Object> transformed, Class<T> resClass) {
        Map<String, Object> constructorArgs = [:]

        transformed.each {
            constructorArgs.put Field.simplifyName(it.key), it.value
        }
        constructorArgs = constructorArgs.findAll { it.value != null }

        resClass.newInstance(constructorArgs)
    }

    def <T> Map getMappedFieldValues(T bean) {
        Map res = [:]
        allFields.each { Field f ->
            String name = f.simpleName
            res.put(name,bean."$name")
        }
        res
    }

}
